<?xml version='1.0' encoding='utf-8'?>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Pro Git - 简体中文版
</title>
    <meta content="http://www.w3.org/1999/xhtml; charset=utf-8" http-equiv="Content-Type"/>
    <link href="stylesheet.css" type="text/css" rel="stylesheet"/>
    <style type="text/css">
		@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }</style>
  </head>
  <body class="calibre">
<h2 id="calibre_toc_18" class="calibre3">记录每次更新到仓库</h2>

<p class="calibre2">现在我们手上已经有了一个真实项目的 Git 仓库，并从这个仓库中取出了所有文件的工作拷贝。接下来，对这些文件作些修改，在完成了一个阶段的目标之后，提交本次更新到仓库。</p>

<p class="calibre2">请记住，工作目录下面的所有文件都不外乎这两种状态：已跟踪或未跟踪。已跟踪的文件是指本来就被纳入版本控制管理的文件，在上次快照中有它们的记录，工作一段时间后，它们的状态可能是未更新，已修改或者已放入暂存区。而所有其他文件都属于未跟踪文件。它们既没有上次更新时的快照，也不在当前的暂存区域。初次克隆某个仓库时，工作目录中的所有文件都属于已跟踪文件，且状态为未修改。</p>

<p class="calibre2">在编辑过某些文件之后，Git 将这些文件标为已修改。我们逐步把这些修改过的文件放到暂存区域，然后等最后一次性提交暂存区域的所有文件更新，如此重复。所以使用 Git 时的文件状态变化周期如图 2-1 所示。</p>

<p class="calibre2"><img src="18333fig0201-tn.png" alt="图 2-1. 文件的状态变化周期" title="图 2-1. 文件的状态变化周期" class="calibre5"/></p>

<h3 id="calibre_toc_88" class="calibre4">检查当前文件状态</h3>

<p class="calibre2">要确定哪些文件当前处于什么状态，可以用 git status 命令。如果在克隆仓库之后立即执行此命令，会看到类似这样的输出：</p>

<pre class="calibre8"><code class="calibre9">$ git status
# On branch master
nothing to commit (working directory clean)
</code></pre>

<p class="calibre2">这说明你现在的工作目录相当干净。换句话说，当前没有任何跟踪着的文件，也没有任何文件在上次提交后更改过。此外，上面的信息还表明，当前目录下没有出现任何处于未跟踪的新文件，否则 Git 会在这里列出来。最后，该命令还显示了当前所在的分支是 master，这是默认的分支名称，实际是可以修改的，现在不必多虑。下一章我们就会详细讨论分支和引用。</p>

<p class="calibre2">现在让我们用 vim 编辑一个新文件 README，保存退出后运行 <code class="calibre9">git status</code> 会看到该文件出现在未跟踪文件列表中：</p>

<pre class="calibre8"><code class="calibre9">$ vim README
$ git status
# On branch master
# Untracked files:
#   (use "git add &lt;file&gt;..." to include in what will be committed)
#
#   README
nothing added to commit but untracked files present (use "git add" to track)
</code></pre>

<p class="calibre2">就是在“Untracked files”这行下面。Git 不会自动将之纳入跟踪范围，除非你明明白白地告诉它这么做，因而不用担心把临时文件什么的也归入版本管理。不过现在我们确实想要跟踪管理 README 这个文件。</p>

<h3 id="calibre_toc_89" class="calibre4">跟踪新文件</h3>

<p class="calibre2">使用命令 <code class="calibre9">git add</code> 开始跟踪一个新文件。所以，要跟踪 README 文件，运行：</p>

<pre class="calibre8"><code class="calibre9">$ git add README
</code></pre>

<p class="calibre2">此时再运行 <code class="calibre9">git status</code> 命令，会看到 README 文件已被跟踪，并处于暂存状态：</p>

<pre class="calibre8"><code class="calibre9">$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#   new file:   README
#
</code></pre>

<p class="calibre2">只要在 “Changes to be committed” 这行下面的，就说明是已暂存状态。如果此时提交，那么该文件此时此刻的版本将被留存在历史记录中。你可能会想起之前我们使用 <code class="calibre9">git init</code> 后就运行了 <code class="calibre9">git add</code> 命令，开始跟踪当前目录下的文件。git add 后可以接要跟踪的文件或目录的路径。如果是目录的话，就说明要递归跟踪所有该目录下的文件。</p>

<h3 id="calibre_toc_90" class="calibre4">暂存已修改文件</h3>

<p class="calibre2">现在我们修改下之前已跟踪过的文件 <code class="calibre9">benchmarks.rb</code>，然后再次运行 <code class="calibre9">status</code> 命令，会看到这样的状态报告：</p>

<pre class="calibre8"><code class="calibre9">$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#   new file:   README
#
# Changed but not updated:
#   (use "git add &lt;file&gt;..." to update what will be committed)
#
#   modified:   benchmarks.rb
#
</code></pre>

<p class="calibre2">文件 benchmarks.rb 出现在 “Changed but not updated” 这行下面，说明已跟踪文件的内容发生了变化，但还没有放到暂存区。要暂存这次更新，需要运行 <code class="calibre9">git add</code> 命令（这是个多功能命令，根据目标文件的状态不同，此命令的效果也不同：可以用它开始跟踪新文件，或者把已跟踪的文件放到暂存区，还能用于合并时把有冲突的文件标记为已解决状态等）。现在让我们运行 <code class="calibre9">git add</code> 将 benchmarks.rb 放到暂存区，然后再看看 <code class="calibre9">git status</code> 的输出：</p>

<pre class="calibre8"><code class="calibre9">$ git add benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#   new file:   README
#   modified:   benchmarks.rb
#
</code></pre>

<p class="calibre2">现在两个文件都已暂存，下次提交时就会一并记录到仓库。假设此时，你想要在 benchmarks.rb 里再加条注释，重新编辑存盘后，准备好提交。不过且慢，再运行 <code class="calibre9">git status</code> 看看：</p>

<pre class="calibre8"><code class="calibre9">$ vim benchmarks.rb 
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#   new file:   README
#   modified:   benchmarks.rb
#
# Changed but not updated:
#   (use "git add &lt;file&gt;..." to update what will be committed)
#
#   modified:   benchmarks.rb
#
</code></pre>

<p class="calibre2">见鬼！benchmarks.rb 文件出现了两次！一次算未暂存，一次算已暂存，这怎么可能呢？好吧，实际上 Git 只不过暂存了你运行 git add 命令时的版本，如果现在提交，那么提交的是添加注释前的版本，而非当前工作目录中的版本。所以，运行了 <code class="calibre9">git add</code> 之后又作了修订的文件，需要重新运行 <code class="calibre9">git add</code> 把最新版本重新暂存起来：</p>

<pre class="calibre8"><code class="calibre9">$ git add benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#   new file:   README
#   modified:   benchmarks.rb
#
</code></pre>

<h3 id="calibre_toc_91" class="calibre4">忽略某些文件</h3>

<p class="calibre2">一般我们总会有些文件无需纳入 Git 的管理，也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件，像是日志或者编译过程中创建的等等。我们可以创建一个名为 .gitignore 的文件，列出要忽略的文件模式，来看一个简单的例子：</p>

<pre class="calibre8"><code class="calibre9">$ cat .gitignore
*.[oa]
*~
</code></pre>

<p class="calibre2">第一行告诉 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的，我们用不着跟踪它们的版本。第二行告诉 Git 忽略所有以波浪符（<code class="calibre9">~</code>）结尾的文件，许多文本编辑软件（比如 Emacs）都用这样的文件名保存副本。此外，你可能还需要忽略 log，tmp 或者 pid 目录，以及自动生成的文档等等。要养成一开始就设置好 .gitignore 文件的习惯，以免将来误提交这类无用的文件。</p>

<p class="calibre2">文件 .gitignore 的格式规范如下：</p>

<ul class="calibre6"><li class="calibre7">所有空行或者以注释符号 ＃ 开头的行都会被 Git 忽略。</li>
<li class="calibre7">可以使用标准的 glob 模式匹配。</li>
<li class="calibre7">匹配模式最后跟反斜杠（<code class="calibre9">/</code>）说明要忽略的是目录。</li>
<li class="calibre7">要忽略指定模式以外的文件或目录，可以在模式前加上惊叹号（<code class="calibre9">!</code>）取反。</li>
</ul><p class="calibre2">所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号（<code class="calibre9">*</code>）匹配零个或多个任意字符；<code class="calibre9">[abc]</code> 匹配任何一个列在方括号中的字符（这个例子要么匹配一个 a，要么匹配一个 b，要么匹配一个 c）；问号（<code class="calibre9">?</code>）只匹配一个任意字符；如果在方括号中使用短划线分隔两个字符，表示所有在这两个字符范围内的都可以匹配（比如 <code class="calibre9">[0-9]</code> 表示匹配所有 0 到 9 的数字）。</p>

<p class="calibre2">我们再看一个 .gitignore 文件的例子：</p>

<pre class="calibre8"><code class="calibre9"># 此为注释 – 将被 Git 忽略
*.a       # 忽略所有 .a 结尾的文件
!lib.a    # 但 lib.a 除外
/TODO     # 仅仅忽略项目根目录下的 TODO 文件，不包括 subdir/TODO
build/    # 忽略 build/ 目录下的所有文件
doc/*.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
</code></pre>

<h3 id="calibre_toc_92" class="calibre4">查看已暂存和未暂存的更新</h3>

<p class="calibre2">实际上 <code class="calibre9">git status</code> 的显示比较简单，仅仅是列出了修改过的文件，如果要查看具体修改了什么地方，可以用 <code class="calibre9">git diff</code> 命令。稍后我们会详细介绍 <code class="calibre9">git diff</code>，不过现在，它已经能回答我们的两个问题了：当前作的哪些更新还没有暂存？有哪些更新已经暂存起来准备好了下次提交？ <code class="calibre9">git diff</code> 会使用文件补丁的格式显示具体添加和删除的行。</p>

<p class="calibre2">假如再次修改 README 文件后暂存，然后编辑 benchmarks.rb 文件后先别暂存，运行 <code class="calibre9">status</code> 命令，会看到：</p>

<pre class="calibre8"><code class="calibre9">$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#   new file:   README
#
# Changed but not updated:
#   (use "git add &lt;file&gt;..." to update what will be committed)
#
#   modified:   benchmarks.rb
#
</code></pre>

<p class="calibre2">要查看尚未暂存的文件更新了哪些部分，不加参数直接输入 <code class="calibre9">git diff</code>：</p>

<pre class="calibre8"><code class="calibre9">$ git diff
diff --git a/benchmarks.rb b/benchmarks.rb
index 3cb747f..da65585 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -36,6 +36,10 @@ def main
           @commit.parents[0].parents[0].parents[0]
         end

+        run_code(x, 'commits 1') do
+          git.commits.size
+        end
+
         run_code(x, 'commits 2') do
           log = git.commits('master', 15)
           log.size
</code></pre>

<p class="calibre2">此命令比较的是工作目录中当前文件和暂存区域快照之间的差异，也就是修改之后还没有暂存起来的变化内容。</p>

<p class="calibre2">若要看已经暂存起来的文件和上次提交时的快照之间的差异，可以用 <code class="calibre9">git diff --cached</code> 命令。（Git 1.6.1 及更高版本还允许使用 <code class="calibre9">git diff --staged</code>，效果是相同的，但更好记些。）来看看实际的效果：</p>

<pre class="calibre8"><code class="calibre9">$ git diff --cached
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README2
@@ -0,0 +1,5 @@
+grit
+ by Tom Preston-Werner, Chris Wanstrath
+ http://github.com/mojombo/grit
+
+Grit is a Ruby library for extracting information from a Git repository
</code></pre>

<p class="calibre2">请注意，单单 <code class="calibre9">git diff</code> 不过是显示还没有暂存起来的改动，而不是这次工作和上次提交之间的差异。所以有时候你一下子暂存了所有更新过的文件后，运行 <code class="calibre9">git diff</code> 后却什么也没有，就是这个原因。</p>

<p class="calibre2">像之前说的，暂存 benchmarks.rb 后再编辑，运行 <code class="calibre9">git status</code> 会看到暂存前后的两个版本：</p>

<pre class="calibre8"><code class="calibre9">$ git add benchmarks.rb
$ echo '# test line' &gt;&gt; benchmarks.rb
$ git status
# On branch master
#
# Changes to be committed:
#
#   modified:   benchmarks.rb
#
# Changed but not updated:
#
#   modified:   benchmarks.rb
#
</code></pre>

<p class="calibre2">现在运行 <code class="calibre9">git diff</code> 看暂存前后的变化：</p>

<pre class="calibre8"><code class="calibre9">$ git diff 
diff --git a/benchmarks.rb b/benchmarks.rb
index e445e28..86b2f7c 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -127,3 +127,4 @@ end
 main()

 ##pp Grit::GitRuby.cache_client.stats 
+# test line
and git diff --cached to see what you’ve staged so far:
$ git diff --cached
diff --git a/benchmarks.rb b/benchmarks.rb
index 3cb747f..e445e28 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -36,6 +36,10 @@ def main
          @commit.parents[0].parents[0].parents[0]
        end

+        run_code(x, 'commits 1') do
+          git.commits.size
+        end
+              
        run_code(x, 'commits 2') do
          log = git.commits('master', 15)
          log.size
</code></pre>

<h3 id="calibre_toc_93" class="calibre4">提交更新</h3>

<p class="calibre2">现在的暂存区域已经准备妥当可以提交了。在此之前，请一定要确认还有什么修改过的或新建的文件还没有 <code class="calibre9">git add</code> 过，否则提交的时候不会记录这些还没暂存起来的变化。所以，每次准备提交前，先用 <code class="calibre9">git status</code> 看下，是不是都已暂存起来了，然后再运行提交命令 <code class="calibre9">git commit</code>：</p>

<pre class="calibre8"><code class="calibre9">$ git commit
</code></pre>

<p class="calibre2">这种方式会启动文本编辑器以便输入本次提交的说明。（默认会启用 shell 的环境变量 $EDITOR 所指定的软件，一般都是 vim 或 emacs。当然也可以按照第一章介绍的方式，使用 <code class="calibre9">git config --global core.editor</code> 命令设定你喜欢的编辑软件。）</p>

<p class="calibre2">编辑器会显示类似下面的文本信息（本例选用 Vim 的屏显方式展示）：</p>

<pre class="calibre8"><code class="calibre9"># Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#       new file:   README
#       modified:   benchmarks.rb 
~
~
~
".git/COMMIT_EDITMSG" 10L, 283C
</code></pre>

<p class="calibre2">可以看到，默认的提交消息包含最后一次运行 <code class="calibre9">git status</code> 的输出，放在注释行里，另外开头还有一空行，供你输入提交说明。你完全可以去掉这些注释行，不过留着也没关系，多少能帮你回想起这次更新的内容有哪些。（如果觉得这还不够，可以用 <code class="calibre9">-v</code> 选项将修改差异的每一行都包含到注释中来。）退出编辑器时，Git 会丢掉注释行，将说明内容和本次更新提交到仓库。</p>

<p class="calibre2">也可以使用 -m 参数后跟提交说明的方式，在一行命令中提交更新：</p>

<pre class="calibre8"><code class="calibre9">$ git commit -m "Story 182: Fix benchmarks for speed"
[master]: created 463dc4f: "Fix benchmarks for speed"
 2 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 README
</code></pre>

<p class="calibre2">好，现在你已经创建了第一个提交！可以看到，提交后它会告诉你，当前是在哪个分支（master）提交的，本次提交的完整 SHA-1 校验和是什么（<code class="calibre9">463dc4f</code>），以及在本次提交中，有多少文件修订过，多少行添改和删改过。</p>

<p class="calibre2">记住，提交时记录的是放在暂存区域的快照，任何还未暂存的仍然保持已修改状态，可以在下次提交时纳入版本管理。每一次运行提交操作，都是对你项目作一次快照，以后可以回到这个状态，或者进行比较。</p>

<h3 id="calibre_toc_94" class="calibre4">跳过使用暂存区域</h3>

<p class="calibre2">尽管使用暂存区域的方式可以精心准备要提交的细节，但有时候这么做略显繁琐。Git 提供了一个跳过使用暂存区域的方式，只要在提交的时候，给 <code class="calibre9">git commit</code> 加上 <code class="calibre9">-a</code> 选项，Git 就会自动把所有已经跟踪过的文件暂存起来一并提交，从而跳过 <code class="calibre9">git add</code> 步骤：</p>

<pre class="calibre8"><code class="calibre9">$ git status
# On branch master
#
# Changed but not updated:
#
#   modified:   benchmarks.rb
#
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
 1 files changed, 5 insertions(+), 0 deletions(-)
</code></pre>

<p class="calibre2">看到了吗？提交之前不再需要 <code class="calibre9">git add</code> 文件 benchmarks.rb 了。</p>

<h3 id="calibre_toc_95" class="calibre4">移除文件</h3>

<p class="calibre2">要从 Git 中移除某个文件，就必须要从已跟踪文件清单中移除（确切地说，是从暂存区域移除），然后提交。可以用 <code class="calibre9">git rm</code> 命令完成此项工作，并连带从工作目录中删除指定的文件，这样以后就不会出现在未跟踪文件清单中了。</p>

<p class="calibre2">如果只是简单地从工作目录中手工删除文件，运行 <code class="calibre9">git status</code> 时就会在 “Changed but not updated” 部分（也就是<em class="calibre11">未暂存</em>清单）看到：</p>

<pre class="calibre8"><code class="calibre9">$ rm grit.gemspec
$ git status
# On branch master
#
# Changed but not updated:
#   (use "git add/rm &lt;file&gt;..." to update what will be committed)
#
#       deleted:    grit.gemspec
#
</code></pre>

<p class="calibre2">然后再运行 <code class="calibre9">git rm</code> 记录此次移除文件的操作：</p>

<pre class="calibre8"><code class="calibre9">$ git rm grit.gemspec
rm 'grit.gemspec'
$ git status
# On branch master
#
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#       deleted:    grit.gemspec
#
</code></pre>

<p class="calibre2">最后提交的时候，该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话，则必须要用强制删除选项 <code class="calibre9">-f</code>（译注：即 force 的首字母），以防误删除文件后丢失修改的内容。</p>

<p class="calibre2">另外一种情况是，我们想把文件从 Git 仓库中删除（亦即从暂存区域移除），但仍然希望保留在当前工作目录中。换句话说，仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 <code class="calibre9">.a</code> 编译文件，不小心纳入仓库后，要移除跟踪但不删除文件，以便稍后在 <code class="calibre9">.gitignore</code> 文件中补上，用 <code class="calibre9">--cached</code> 选项即可：</p>

<pre class="calibre8"><code class="calibre9">$ git rm --cached readme.txt
</code></pre>

<p class="calibre2">后面可以列出文件或者目录的名字，也可以使用 glob 模式。比方说：</p>

<pre class="calibre8"><code class="calibre9">$ git rm log/\*.log
</code></pre>

<p class="calibre2">注意到星号 <code class="calibre9">*</code> 之前的反斜杠 <code class="calibre9">\</code>，因为 Git 有它自己的文件模式扩展匹配方式，所以我们不用 shell 来帮忙展开（译注：实际上不加反斜杠也可以运行，只不过按照 shell 扩展的话，仅仅删除指定目录下的文件而不会递归匹配。上面的例子本来就指定了目录，所以效果等同，但下面的例子就会用递归方式匹配，所以必须加反斜杠。）。此命令删除所有 <code class="calibre9">log/</code> 目录下扩展名为 <code class="calibre9">.log</code> 的文件。类似的比如：</p>

<pre class="calibre8"><code class="calibre9">$ git rm \*~
</code></pre>

<p class="calibre2">会递归删除当前目录及其子目录中所有 <code class="calibre9">~</code> 结尾的文件。</p>

<h3 id="calibre_toc_96" class="calibre4">移动文件</h3>

<p class="calibre2">不像其他的 VCS 系统，Git 并不跟踪文件移动操作。如果在 Git 中重命名了某个文件，仓库中存储的元数据并不会体现出这是一次改名操作。不过 Git 非常聪明，它会推断出究竟发生了什么，至于具体是如何做到的，我们稍后再谈。</p>

<p class="calibre2">既然如此，当你看到 Git 的 <code class="calibre9">mv</code> 命令时一定会困惑不已。要在 Git 中对文件改名，可以这么做：</p>

<pre class="calibre8"><code class="calibre9">$ git mv file_from file_to
</code></pre>

<p class="calibre2">它会恰如预期般正常工作。实际上，即便此时查看状态信息，也会明白无误地看到关于重命名操作的说明：</p>

<pre class="calibre8"><code class="calibre9">$ git mv README.txt README
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# Changes to be committed:
#   (use "git reset HEAD &lt;file&gt;..." to unstage)
#
#       renamed:    README.txt -&gt; README
#
</code></pre>

<p class="calibre2">其实，运行 <code class="calibre9">git mv</code> 就相当于运行了下面三条命令：</p>

<pre class="calibre8"><code class="calibre9">$ mv README.txt README
$ git rm README.txt
$ git add README
</code></pre>

<p class="calibre2">如此分开操作，Git 也会意识到这是一次改名，所以不管何种方式都一样。当然，直接用 <code class="calibre9">git mv</code> 轻便得多，不过有时候用其他工具批处理改名的话，要记得在提交前删除老的文件名，再添加新的文件名。</p>

</body>
</html>
